﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using FFmpegVideoClip.Configuration;
using FFmpegVideoClip.Processing;
using UtilityToolsCollect.ObjectsLibrary.ProcessManage;


namespace FFmpegVideoClip.Lib.AudioSpectrogram
{
    public record class AudioWavSpectrogramResult(int ZoomLevel, ImageBrush Result);
    public class AudioWavSpectrogramCancelSource
    {
        private static SpinLock m_SpimLock = new ();
        private static List<AudioWavSpectrogramCancelSource> CancelSourceList = new();
        public static AudioWavSpectrogramCancelSource Create() { AudioWavSpectrogramCancelSource result = new(); Add(result); return result; }
        private static void Add(AudioWavSpectrogramCancelSource CancelSource) { bool m_LockTaken = false; m_SpimLock.Enter(ref m_LockTaken); CancelSourceList.Add(CancelSource); m_SpimLock.Exit(); }
        private static void Remove(AudioWavSpectrogramCancelSource CancelSource) { bool m_LockTaken = false; m_SpimLock.Enter(ref m_LockTaken); CancelSourceList.Remove(CancelSource); m_SpimLock.Exit(); }
        public static void CancelAll() { CancelSourceList.ToArray().ForEach(p => { if (!p.IsCancellationRequested) p.Cancel(); p.Clear(); }); }
        //******************************************************//
        private AudioWavSpectrogramCancelSource() { CancelSource = new(); }
        private object LockToken = new();
        public bool IsCancellationRequested { get; private set; } = false;
        public bool IsClear { get; private set; }
        public event Action? OnCancel;
        public CancellationTokenSource CancelSource;
        public CancellationToken Token => IsClear == false ? CancelSource.Token : throw new ObjectDisposedException($"当前[{GetType().FullName}]，中的类型为[{typeof(CancellationToken).FullName}]的属性[{nameof(Token)}]获取失败，此对象以调用[{nameof(Clear)}]方法进行清理，并等待CLR回收，该对象已丢弃，无法继续使用");
        public void Cancel() { lock (LockToken) { if (IsClear) return; CancelSource.Cancel(); OnCancel?.Invoke(); IsCancellationRequested = true; } }
        protected virtual void OnClear() { Remove(this); CancelSource.Dispose(); CancelSource = null!; OnCancel = null; }
        public void Clear() { lock (LockToken) { if (!IsClear) { OnClear(); IsClear = true; } } }
    }
    public class AudioWavSpectrogram
    {
        public static Color[] WavSpectrogramColors = { Colors.Black, Colors.White, Colors.Red, Colors.Yellow, Colors.Blue, Colors.LimeGreen };
        [Obsolete]private class WavSpectrogramProcessInfo
        {
            private StringBuilder m_RunInfo = new StringBuilder();
            public string RunInfo => m_RunInfo.ToString();
            public bool IsRunInfoEmpty => m_RunInfo.Length < 1;
            private Regex VolumeMatch = new Regex("(?<=max_volume:\\s*)-?\\d\\.?\\d", RegexOptions.Compiled|RegexOptions.IgnoreCase);
            public bool IsError { get; private set; } = true;
            public float? MaxVolume { get; private set; } = null;
            public void SetTaskInfo(string? value)
            {
                if (string.IsNullOrWhiteSpace(value))
                    return;
                else if (float.TryParse(VolumeMatch.Match(value).Value, out float Volume))
                    MaxVolume = Volume;
                else if (Config.FFmpegSuccessValidated.IsMatch(value))
                    IsError = false;
                m_RunInfo.AppendLine(value);
            }
        }
        [StructLayout(LayoutKind.Sequential)] private struct DEVMODE
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
            public string dmDeviceName;
            public short dmSpecVersion;
            public short dmDriverVersion;
            public short dmSize;
            public short dmDriverExtra;
            public int dmFields;
            public int dmPositionX;
            public int dmPositionY;
            public ScreenOrientation dmDisplayOrientation;
            public int dmDisplayFixedOutput;
            public short dmColor;
            public short dmDuplex;
            public short dmYResolution;
            public short dmTTOption;
            public short dmCollate;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
            public string dmFormName;
            public short dmLogPixels;
            public int dmBitsPerPel;
            public int dmPelsWidth;
            public int dmPelsHeight;
            public int dmDisplayFlags;
            public int dmDisplayFrequency;
            public int dmICMMethod;
            public int dmICMIntent;
            public int dmMediaType;
            public int dmDitherType;
            public int dmReserved1;
            public int dmReserved2;
            public int dmPanningWidth;
            public int dmPanningHeight;
        }
        private const int ENUM_CURRENT_SETTINGS = -1;
        private const byte AlphaTransparentIimit = (byte)(255 * 0.2f);
        private static int m_ScreenWidth;
        private static ImageBrush? m_BlankSpectrogramImageBrush;
        [DllImport("user32.dll")] private static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);
        public static ImageBrush BlankSpectrogramImageBrush 
        {
            get 
            {
                if(m_BlankSpectrogramImageBrush != null)
                    return m_BlankSpectrogramImageBrush;
                m_BlankSpectrogramImageBrush = new(BitmapSource.Create(1, 1, 0, 0, PixelFormats.Bgra32, null, new byte[] { 0, 0, 0, 0 }, 4));
                return m_BlankSpectrogramImageBrush;
            }
        }
        public static int ScreenWidth=>MainWindow.desktopSize.Width;
        [Obsolete]public static int ScreenWidth2
        {
            get
            {
                if (m_ScreenWidth > 0)
                    return m_ScreenWidth;
                foreach (Screen screen in Screen.AllScreens)
                {
                    DEVMODE dm = default;
                    dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));
                    EnumDisplaySettings(screen.DeviceName, ENUM_CURRENT_SETTINGS, ref dm);
                    if (m_ScreenWidth < dm.dmPelsWidth)
                        m_ScreenWidth = dm.dmPelsWidth;
                }
                return m_ScreenWidth;
            }
        }
        public static int Imageheight = 450;
        private static BitmapImage ByteArrayToBitmapImage(byte[] byteArray)
        {
            BitmapImage bmp = new BitmapImage();
            bmp.BeginInit();
            bmp.StreamSource = new MemoryStream(byteArray);
            bmp.EndInit();
            return bmp;
        }
        private static string[] GetFFmpegArgs(int ZoomLevel, string FilePath, TimeSpan Start, TimeSpan End, float Volume)
        {
            if (ZoomLevel == 0)
                return [$"-i \"{FilePath}\" -filter_complex \"aformat=channel_layouts=mono,showwavespic=s={ScreenWidth}x{Imageheight}:colors=white\" -frames:v 1 -pix_fmt bgra -f rawvideo -"];
            else
            {
                if (Start >= End)
                    throw new ArgumentOutOfRangeException($"时间范围错误:结束时间小于或等于开始时间，EndTime[{Tools.FormatTimeSpanToMediaTime(End)}] 必须大于 StartTime[{Tools.FormatTimeSpanToMediaTime(Start)}]");
                if (Volume != 0)
                    Volume = -Volume;
                return new string[] {
                    Start == TimeSpan.Zero?
                    $"-t {Tools.FormatTimeSpanToMediaTime(End)} -accurate_seek -i \"{FilePath}\" -vn -ac 1 -af \"volume={Volume}dB\" -c:a pcm_s16le -f s16le -"
                    :$"-ss {Tools.FormatTimeSpanToMediaTime(Start)} -to {Tools.FormatTimeSpanToMediaTime(End)} -accurate_seek -i \"{FilePath}\" -vn -ac 1 -af \"volume={Volume}dB\" -c:a pcm_s16le -f s16le -",
                    $"-f s16le -i pipe:0 -filter_complex \"showwavespic=s={ScreenWidth}x{Imageheight}:colors=white\" -frames:v 1 -pix_fmt bgra -f rawvideo -" };
            }
        }
        [Obsolete]private static byte[]? FFmpegReadFile(string arg, AudioWavSpectrogramCancelSource CancellSource, WavSpectrogramProcessInfo WavSpectrogramInfo, Action<string>? OutInfo)
        {
            byte[]? result;
            Process? ffmpeg = new();
            ProcessShell ffmepgShell = ProcessShell.Create(ffmpeg);
            try
            {
                CancellSource.OnCancel += () => ffmepgShell.Kill(false);
                ffmpeg.StartInfo.FileName = LosslessSplitTask.FFProcessor;
                ffmpeg.StartInfo.Arguments = arg;
                ffmpeg.StartInfo.UseShellExecute = false;
                ffmpeg.StartInfo.CreateNoWindow = true;
                ffmpeg.StartInfo.RedirectStandardOutput = true;
                ffmpeg.StartInfo.RedirectStandardError = true;

                ffmpeg.ErrorDataReceived += (s, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) { OutInfo?.Invoke(e.Data); WavSpectrogramInfo.SetTaskInfo(e.Data); } };

                ffmpeg.Start();
                ffmpeg.BeginErrorReadLine();


                using (MemoryStream ms = new())
                {
                    ffmpeg.StandardOutput.BaseStream.CopyTo(ms);
                    result = ms.ToArray();
                }

                ffmpeg?.WaitForExit();
                if (CancellSource.IsCancellationRequested)
                    return null;
                return result;
            }
            catch (Exception exc) { LogManage.WriteExceptionInfo($"FFmpeg执行异常:参数[{arg}]", exc, true); return null; }
            finally { ffmepgShell.Dispose(); CancellSource.Clear(); }
        }
        [Obsolete]private static byte[]? FFmpegReadWavSpectrogram(string[] args, AudioWavSpectrogramCancelSource CancellSource, WavSpectrogramProcessInfo CutInfo, WavSpectrogramProcessInfo  WavSpectrogramInfo, Action<string>? OutInfo = null)
        {
            byte[] bytes;
            Process? ffmpegWavSpectrogram = new();
            Process? ffmpegCut = new();
            ProcessShell ffmpegWavSpectrogramShell = ProcessShell.Create(ffmpegWavSpectrogram);
            ProcessShell ffmpegCutShell = ProcessShell.Create(ffmpegCut);
            CancellSource.OnCancel += () => { ffmpegWavSpectrogramShell.Kill(false); ffmpegCutShell.Kill(false); };
            try
            {
                ffmpegWavSpectrogram.StartInfo.FileName = LosslessSplitTask.FFProcessor;
                ffmpegWavSpectrogram.StartInfo.Arguments = args[1];
                ffmpegWavSpectrogram.StartInfo.UseShellExecute = false;
                ffmpegWavSpectrogram.StartInfo.CreateNoWindow = true;
                ffmpegWavSpectrogram.StartInfo.RedirectStandardInput = true;
                ffmpegWavSpectrogram.StartInfo.RedirectStandardOutput = true;
                ffmpegWavSpectrogram.StartInfo.RedirectStandardError = true;

                ffmpegCut.StartInfo.FileName = LosslessSplitTask.FFProcessor;
                ffmpegCut.StartInfo.Arguments = args[0];
                ffmpegCut.StartInfo.UseShellExecute = false;
                ffmpegCut.StartInfo.CreateNoWindow = true;
                ffmpegCut.StartInfo.RedirectStandardOutput = true;
                ffmpegCut.StartInfo.RedirectStandardError = true;

                ffmpegCut.ErrorDataReceived += (s, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) { OutInfo?.Invoke(e.Data); CutInfo.SetTaskInfo(e.Data); } };
                ffmpegWavSpectrogram.ErrorDataReceived += (s, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) { OutInfo?.Invoke(e.Data); WavSpectrogramInfo.SetTaskInfo(e.Data); } };

                ffmpegWavSpectrogram.Start();
                ffmpegWavSpectrogram.BeginErrorReadLine();
                ffmpegCut.Start();
                ffmpegCut.BeginErrorReadLine();

                ffmpegCut?.StandardOutput.BaseStream.CopyTo(ffmpegWavSpectrogram.StandardInput.BaseStream);
                ffmpegWavSpectrogram?.StandardInput.BaseStream.Flush();
                ffmpegWavSpectrogram?.StandardInput.BaseStream.Close();

                ffmpegCut?.WaitForExit();
                if (CutInfo?.IsError ?? false)
                    return null;

                using (MemoryStream ms = new())
                {
                    ffmpegWavSpectrogram?.StandardOutput.BaseStream.CopyTo(ms);
                    bytes = ms.ToArray();
                }
                ffmpegWavSpectrogram?.WaitForExit();
               
                if (CancellSource.IsCancellationRequested)
                    return null;
                return bytes;
            }
            catch (Exception exc) { LogManage.WriteExceptionInfo($"FFmpeg执行异常:参数[{args[0]}][{args[1]}]", exc, true); return null; }
            finally { ffmpegWavSpectrogramShell.Dispose(); ffmpegCutShell.Dispose(); CancellSource.Clear(); }
        }
        public static byte[]? FFmpegRead(int ZoomLevel, string FilePath, AudioWavSpectrogramCancelSource CancellSource, Action<string>? OutInfo = null, TimeSpan Start = default, TimeSpan End = default, float Volume = 0)
        {
            // byte[]? bytes;
            // string[] args = GetFFmpegArgs(ZoomLevel, FilePath, Start, End, Volume);
            // args.ForEach(p => LogManage.WriteLineNewInfo(p));
            // WavSpectrogramProcessInfo WavSpectrogramInfo = new WavSpectrogramProcessInfo();
            if (ZoomLevel == 0)
            {
                if (Volume == 0)
                {
                    Volume = VolumeDetector.DetectVolume(FilePath, CancellSource.Token, TimeSpan.Zero, TimeSpan.MaxValue).MaxVolume_volumedetect;
                }
                return Showwavespic.GetAudioWavSpectrogramImageData(FilePath, Volume, ScreenWidth, Imageheight, CancellSource.Token, TimeSpan.Zero, TimeSpan.MaxValue, FFmpeg.AutoGen.AVPixelFormat.AV_PIX_FMT_BGRA);
                // bytes = FFmpegReadFile(args[0], CancellSource, WavSpectrogramInfo, OutInfo);
                // if (WavSpectrogramInfo.IsError)
                // {
                //     LogManage.WriteLineNewInfo("{");
                //     LogManage.WriteLineNewInfo($"获取波形图异常: ZoomLevel={ZoomLevel};Args:[{args[0]}];FFmpegRunInfo:");
                //     if (!WavSpectrogramInfo.IsRunInfoEmpty)
                //         LogManage.WriteNewInfo(WavSpectrogramInfo.RunInfo);
                //     else LogManage.WriteNewInfo("Empty");
                //     LogManage.WriteLineNewInfo("}");
                //     return null;
                // }
                // return bytes;
            }
            return Showwavespic.GetAudioWavSpectrogramImageData(FilePath, -Volume, ScreenWidth, Imageheight, CancellSource.Token, Start, End, FFmpeg.AutoGen.AVPixelFormat.AV_PIX_FMT_BGRA);
            // WavSpectrogramProcessInfo CutInfo = new WavSpectrogramProcessInfo();
            // bytes = FFmpegReadWavSpectrogram(args, CancellSource, CutInfo, WavSpectrogramInfo, OutInfo);
            // if (CutInfo.IsError || WavSpectrogramInfo.IsError)
            // {
            //     LogManage.WriteLineNewInfo("{");
            //     LogManage.WriteLineNewInfo($"获取波形图异常: ZoomLevel={ZoomLevel};FFmpegRunInfo:");

            //     LogManage.WriteNewInfo($"Args:{args[0]}");
            //     if (!CutInfo.IsRunInfoEmpty)
            //         LogManage.WriteNewInfo($"CutInfo:{CutInfo.RunInfo}");
            //     else LogManage.WriteNewInfo("Empty");

            //     LogManage.WriteNewInfo($"Args:{args[1]}");
            //     if (!WavSpectrogramInfo.IsRunInfoEmpty)
            //         LogManage.WriteNewInfo($"WavSpectrogramInfo:{WavSpectrogramInfo.RunInfo}");
            //     else LogManage.WriteNewInfo("Empty");
            //     LogManage.WriteLineNewInfo("}");
            //     return null;
            // }
            // return bytes;
        }
        
        
        [Obsolete]private static float internalGetFileMaxVolume(string arg, WavSpectrogramProcessInfo processInfo, AudioWavSpectrogramCancelSource CancellSource)
        {
            if (processInfo == null)
                processInfo = new();
            Process? ffmpeg = new();
            ProcessShell ffmepgShell = ProcessShell.Create(ffmpeg);
            CancellSource.OnCancel += () => ffmepgShell.Kill(false);
            try
            {
                ffmpeg.StartInfo.FileName = LosslessSplitTask.FFProcessor;
                ffmpeg.StartInfo.Arguments = arg;
                ffmpeg.StartInfo.UseShellExecute = false;
                ffmpeg.StartInfo.CreateNoWindow = true;
                ffmpeg.StartInfo.RedirectStandardError = true;

                ffmpeg.ErrorDataReceived += (s, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) processInfo.SetTaskInfo(e.Data); };

                ffmpeg.Start();
                ffmpeg.BeginErrorReadLine();

                ffmpeg?.WaitForExit();

                if (CancellSource.IsCancellationRequested)
                    throw new OperationCanceledException("获取音量:操作执行被取消");
                if (processInfo.IsError || !processInfo.MaxVolume.HasValue)
                    throw new ProcessExecuteException($"获取音量执行异常:执行参数:{arg}");
                return processInfo.MaxVolume.Value;
            }
            finally { ffmepgShell.Dispose(); CancellSource.Clear(); }
        }
        public static float GetFileMaxVolume(string FilePath, AudioWavSpectrogramCancelSource CancellSource, TimeSpan? startTime, TimeSpan? endTime)
            => VolumeDetector.DetectVolume(FilePath, CancellSource.Token, startTime, endTime).MaxVolume_volumedetect;
        [Obsolete]public static float GetFileMaxVolume_old(string FilePath, AudioWavSpectrogramCancelSource CancellSource, TimeSpan? startTime, TimeSpan? endTime)
        {
            string arg = $"-vn -sn -dn -i \"{FilePath}\" -af volumedetect -f null /dev/null";
            float maxVolume = 0f;
            WavSpectrogramProcessInfo processInfo = new();
            try { maxVolume = internalGetFileMaxVolume(arg, processInfo, CancellSource); }
            catch (OperationCanceledException) { }
            catch (Exception exc)
            {
                LogManage.WriteLineNewInfo("{");
                LogManage.WriteExceptionInfo($"获取文件音量错误:参数[{arg}]", exc, true);
                if (!processInfo.IsRunInfoEmpty)
                    LogManage.WriteLineNewInfo($"执行信息：\r\n{processInfo.RunInfo}");
                LogManage.WriteLineNewInfo("}");
            }
            return maxVolume;
        }
        public static async Task<float> GetFileMaxVolumeAnsy(string FilePath, AudioWavSpectrogramCancelSource CancellSource,TimeSpan? startTime, TimeSpan? endTime) => await Task.Run(() => GetFileMaxVolume(FilePath, CancellSource, startTime, endTime), CancellSource.Token);


        private static BitmapSource? internalCreateWavSpectrogramPng(int ZoomLevel, string FilePath, AudioWavSpectrogramCancelSource CancellSource, TimeSpan Start = default, TimeSpan End = default, float Volume = 0)
        {
            byte[]? pixels;
            try
            {
                pixels = FFmpegRead(ZoomLevel, FilePath, CancellSource, null, Start, End, Volume);
            }
            catch (Exception exc)
            {
                LogManage.WriteExceptionInfo($"生成波形失败,未返回有效数据:ZoomLevel{ZoomLevel}|Start{Start}|End{End}|Volume{Volume}|文件路径:[{FilePath}]", exc);
                return null;
            }
            if (CancellSource.IsCancellationRequested)
                return null;

            if (pixels == null || pixels.Length < 1)
            {
                LogManage.WriteLineNewInfo($"生成波形失败,未返回有效数据:ZoomLevel{ZoomLevel}|Start{Start}|End{End}|Volume{Volume}|文件路径:[{FilePath}]");
                return null;
                //if (ZoomLevel > 0)
                //    Volume = GetFileMaxVolume(FilePath, CancellSource, Start, End);
                // string[] RunArgs = GetFFmpegArgs(ZoomLevel, FilePath, Start, End, Volume);
                //throw new ProcessExecuteException($"生成波形失败：\r\n  执行参数1:[{RunArgs[0]}]\r\n  执行参数2:[{RunArgs[1]}]");
            }
            int stride = ScreenWidth * 4;
            //BitmapSource sourceTest = BitmapSource.Create(ScreenWidth, Imageheight, 0, 0, PixelFormats.Bgra32, null, pixels, stride);
            //sourceTest.Freeze();
            //return sourceTest;
            //枚举像素透明，侦测最大有图形的边界
            Tools.FindImageBoundsSIMD(pixels, AlphaTransparentIimit, ScreenWidth, out int TopBounds, out int BottomBounds);
            LogManage.WriteLineNewInfo($"原始大小:{{ScreenWidth:{ScreenWidth}|Imageheight:{Imageheight}|字节:{pixels.Length}}}, stride:{stride} ,边界:{{Top{TopBounds}|Low{BottomBounds}|height{BottomBounds - TopBounds}}}");
            Int32Rect rect = new Int32Rect(0, TopBounds, ScreenWidth, BottomBounds - TopBounds);
            if (rect.Height < 1)
                return null;
            try
            {
                BitmapSource source = BitmapSource.Create(rect.Width, rect.Height, 0, 0, PixelFormats.Bgra32, null, pixels[(TopBounds * stride)..((BottomBounds + 1) * stride - 1)], stride);
                LogManage.WriteLineNewInfo($"BitmapSource:{source.Width}:{source.Height}");
                source.Freeze();
                return source;
            }
            catch (Exception ex) { LogManage.WriteExceptionInfo("生成波形图[BitmapSource.Create]阶段失败", ex); return null; }
            
        }
        public static AudioWavSpectrogramResult? CreateWavSpectrogramPng(int ZoomLevel, string FilePath, AudioWavSpectrogramCancelSource CancellSource, TimeSpan Start = default, TimeSpan End = default, float Volume = 0)
        {
            ImageBrush? bs = BlankSpectrogramImageBrush;
            try
            {
                BitmapSource? _bs = internalCreateWavSpectrogramPng(ZoomLevel, FilePath, CancellSource, Start, End, Volume);
                if (_bs is not null)
                    bs = System.Windows.Application.Current.Dispatcher.Invoke(() => new ImageBrush(_bs));
            }
            catch (OperationCanceledException) { }
            catch (Exception exc) { LogManage.WriteExceptionInfo("生成波形异常:", exc, true); }
            if (bs == null)
                return null;
            return new AudioWavSpectrogramResult(ZoomLevel, bs);
        }
        public static async Task<AudioWavSpectrogramResult?> CreateWavSpectrogramPngAsync(int ZoomLevel, string FilePath, AudioWavSpectrogramCancelSource CancellSource, TimeSpan Start = default, TimeSpan End = default, float Volume = 0)
            => await Task.Run(() => CreateWavSpectrogramPng(ZoomLevel, FilePath, CancellSource, Start, End, Volume), CancellSource.Token);



        [Serializable]
        public class AudioSpectrogramFFmpegException : Exception
        {
            public AudioSpectrogramFFmpegException() { }
            public AudioSpectrogramFFmpegException(string message) : base(message) { }
            public AudioSpectrogramFFmpegException(string message, Exception inner) : base(message, inner) { }
            [Obsolete]protected AudioSpectrogramFFmpegException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
        }
    }

    

}
